/*
 * Copyright (c) 2020, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Copyright (C) 2015 Samuel Audet
 *
 * Licensed either under the Apache License, Version 2.0, or (at your option)
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation (subject to the "Classpath" exception),
 * either version 2, or any later version (collectively, the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     http://www.gnu.org/licenses/
 *     http://www.gnu.org/software/classpath/license.html
 *
 * or as provided in the LICENSE.txt file that accompanied this code.
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bytedeco.copiedstuff;

import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.nio.*;

/**
 * A utility class to copy data between {@link Frame} and {@link BufferedImage}.
 * Since {@link BufferedImage} does not support NIO buffers, we cannot share
 * allocated memory with {@link Frame}.
 *
 * @author Samuel Audet
 */
@SuppressWarnings({"UnsafeFinalization", "MissingOverride", "NarrowingCompoundAssignment"})
public class Java2DFrameConverter extends FrameConverter<BufferedImage> {

	@Override public Frame convert( BufferedImage img ) {
		return getFrame(img);
	}

	@Override public BufferedImage convert( Frame frame ) {
		return getBufferedImage(frame);
	}

	public static BufferedImage cloneBufferedImage( BufferedImage bufferedImage ) {
		if (bufferedImage == null) {
			return null;
		}
		BufferedImage bi = bufferedImage;
		int type = bi.getType();
		if (type == BufferedImage.TYPE_CUSTOM) {
			return new BufferedImage(bi.getColorModel(),
					bi.copyData(null), bi.isAlphaPremultiplied(), null);
		} else {
			return new BufferedImage(bi.getWidth(), bi.getHeight(), type);
		}
	}

	public static final byte[]
			gamma22 = new byte[256],
			gamma22inv = new byte[256];

	static {
		for (int i = 0; i < 256; i++) {
			gamma22[i] = (byte)Math.round(Math.pow(i/255.0, 2.2)*255.0);
			gamma22inv[i] = (byte)Math.round(Math.pow(i/255.0, 1/2.2)*255.0);
		}
	}

	public static int decodeGamma22( int value ) {
		return gamma22[value & 0xFF] & 0xFF;
	}

	public static int encodeGamma22( int value ) {
		return gamma22inv[value & 0xFF] & 0xFF;
	}

	public static void flipCopyWithGamma( ByteBuffer srcBuf, int srcStep,
										  ByteBuffer dstBuf, int dstStep, boolean signed, double gamma, boolean flip, int channels ) {
		assert srcBuf != dstBuf;
		int w = Math.min(srcStep, dstStep);
		int srcLine = srcBuf.position(), dstLine = dstBuf.position();
		byte[] buffer = new byte[channels];
		while (srcLine < srcBuf.capacity() && dstLine < dstBuf.capacity()) {
			if (flip) {
				srcBuf.position(srcBuf.capacity() - srcLine - srcStep);
			} else {
				srcBuf.position(srcLine);
			}
			dstBuf.position(dstLine);
			w = Math.min(Math.min(w, srcBuf.remaining()), dstBuf.remaining());
			if (signed) {
				if (channels > 1) {
					for (int x = 0; x < w; x += channels) {
						for (int z = 0; z < channels; z++) {
							int in = srcBuf.get();
							byte out;
							if (gamma == 1.0) {
								out = (byte)in;
							} else {
								out = (byte)Math.round(Math.pow((double)in/Byte.MAX_VALUE, gamma)*Byte.MAX_VALUE);
							}
							buffer[z] = out;
						}
						for (int z = channels - 1; z >= 0; z--) {
							dstBuf.put(buffer[z]);
						}
					}
				} else {
					for (int x = 0; x < w; x++) {
						int in = srcBuf.get();
						byte out;
						if (gamma == 1.0) {
							out = (byte)in;
						} else {
							out = (byte)Math.round(Math.pow((double)in/Byte.MAX_VALUE, gamma)*Byte.MAX_VALUE);
						}
						dstBuf.put(out);
					}
				}
			} else {
				if (channels > 1) {
					for (int x = 0; x < w; x += channels) {
						for (int z = 0; z < channels; z++) {
							byte out;
							int in = srcBuf.get() & 0xFF;
							if (gamma == 1.0) {
								out = (byte)in;
							} else if (gamma == 2.2) {
								out = gamma22[in];
							} else if (gamma == 1/2.2) {
								out = gamma22inv[in];
							} else {
								out = (byte)Math.round(Math.pow((double)in/0xFF, gamma)*0xFF);
							}
							buffer[z] = out;
						}
						for (int z = channels - 1; z >= 0; z--) {
							dstBuf.put(buffer[z]);
						}
					}
				} else {
					for (int x = 0; x < w; x++) {
						byte out;
						int in = srcBuf.get() & 0xFF;
						if (gamma == 1.0) {
							out = (byte)in;
						} else if (gamma == 2.2) {
							out = gamma22[in];
						} else if (gamma == 1/2.2) {
							out = gamma22inv[in];
						} else {
							out = (byte)Math.round(Math.pow((double)in/0xFF, gamma)*0xFF);
						}
						dstBuf.put(out);
					}
				}
			}
			srcLine += srcStep;
			dstLine += dstStep;
		}
	}

	public static void flipCopyWithGamma( ShortBuffer srcBuf, int srcStep,
										  ShortBuffer dstBuf, int dstStep, boolean signed, double gamma, boolean flip, int channels ) {
		assert srcBuf != dstBuf;
		int w = Math.min(srcStep, dstStep);
		int srcLine = srcBuf.position(), dstLine = dstBuf.position();
		short[] buffer = new short[channels];
		while (srcLine < srcBuf.capacity() && dstLine < dstBuf.capacity()) {
			if (flip) {
				srcBuf.position(srcBuf.capacity() - srcLine - srcStep);
			} else {
				srcBuf.position(srcLine);
			}
			dstBuf.position(dstLine);
			w = Math.min(Math.min(w, srcBuf.remaining()), dstBuf.remaining());
			if (signed) {
				if (channels > 1) {
					for (int x = 0; x < w; x += channels) {
						for (int z = 0; z < channels; z++) {
							int in = srcBuf.get();
							short out;
							if (gamma == 1.0) {
								out = (short)in;
							} else {
								out = (short)Math.round(Math.pow((double)in/Short.MAX_VALUE, gamma)*Short.MAX_VALUE);
							}
							buffer[z] = out;
						}
						for (int z = channels - 1; z >= 0; z--) {
							dstBuf.put(buffer[z]);
						}
					}
				} else {
					for (int x = 0; x < w; x++) {
						int in = srcBuf.get();
						short out;
						if (gamma == 1.0) {
							out = (short)in;
						} else {
							out = (short)Math.round(Math.pow((double)in/Short.MAX_VALUE, gamma)*Short.MAX_VALUE);
						}
						dstBuf.put(out);
					}
				}
			} else {
				if (channels > 1) {
					for (int x = 0; x < w; x += channels) {
						for (int z = 0; z < channels; z++) {
							int in = srcBuf.get();
							short out;
							if (gamma == 1.0) {
								out = (short)in;
							} else {
								out = (short)Math.round(Math.pow((double)in/0xFFFF, gamma)*0xFFFF);
							}
							buffer[z] = out;
						}
						for (int z = channels - 1; z >= 0; z--) {
							dstBuf.put(buffer[z]);
						}
					}
				} else {
					for (int x = 0; x < w; x++) {
						int in = srcBuf.get() & 0xFFFF;
						short out;
						if (gamma == 1.0) {
							out = (short)in;
						} else {
							out = (short)Math.round(Math.pow((double)in/0xFFFF, gamma)*0xFFFF);
						}
						dstBuf.put(out);
					}
				}
			}
			srcLine += srcStep;
			dstLine += dstStep;
		}
	}

	public static void flipCopyWithGamma( IntBuffer srcBuf, int srcStep,
										  IntBuffer dstBuf, int dstStep, double gamma, boolean flip, int channels ) {
		assert srcBuf != dstBuf;
		int w = Math.min(srcStep, dstStep);
		int srcLine = srcBuf.position(), dstLine = dstBuf.position();
		int[] buffer = new int[channels];
		while (srcLine < srcBuf.capacity() && dstLine < dstBuf.capacity()) {
			if (flip) {
				srcBuf.position(srcBuf.capacity() - srcLine - srcStep);
			} else {
				srcBuf.position(srcLine);
			}
			dstBuf.position(dstLine);
			w = Math.min(Math.min(w, srcBuf.remaining()), dstBuf.remaining());
			if (channels > 1) {
				for (int x = 0; x < w; x += channels) {
					for (int z = 0; z < channels; z++) {
						int in = srcBuf.get();
						int out;
						if (gamma == 1.0) {
							out = (int)in;
						} else {
							out = (int)Math.round(Math.pow((double)in/Integer.MAX_VALUE, gamma)*Integer.MAX_VALUE);
						}
						buffer[z] = out;
					}
					for (int z = channels - 1; z >= 0; z--) {
						dstBuf.put(buffer[z]);
					}
				}
			} else {
				for (int x = 0; x < w; x++) {
					int in = srcBuf.get();
					int out;
					if (gamma == 1.0) {
						out = in;
					} else {
						out = (int)Math.round(Math.pow((double)in/Integer.MAX_VALUE, gamma)*Integer.MAX_VALUE);
					}
					dstBuf.put(out);
				}
			}
			srcLine += srcStep;
			dstLine += dstStep;
		}
	}

	public static void flipCopyWithGamma( FloatBuffer srcBuf, int srcStep,
										  FloatBuffer dstBuf, int dstStep, double gamma, boolean flip, int channels ) {
		assert srcBuf != dstBuf;
		int w = Math.min(srcStep, dstStep);
		int srcLine = srcBuf.position(), dstLine = dstBuf.position();
		float[] buffer = new float[channels];
		while (srcLine < srcBuf.capacity() && dstLine < dstBuf.capacity()) {
			if (flip) {
				srcBuf.position(srcBuf.capacity() - srcLine - srcStep);
			} else {
				srcBuf.position(srcLine);
			}
			dstBuf.position(dstLine);
			w = Math.min(Math.min(w, srcBuf.remaining()), dstBuf.remaining());
			if (channels > 1) {
				for (int x = 0; x < w; x += channels) {
					for (int z = 0; z < channels; z++) {
						float in = srcBuf.get();
						float out;
						if (gamma == 1.0) {
							out = in;
						} else {
							out = (float)Math.pow(in, gamma);
						}
						buffer[z] = out;
					}
					for (int z = channels - 1; z >= 0; z--) {
						dstBuf.put(buffer[z]);
					}
				}
			} else {
				for (int x = 0; x < w; x++) {
					float in = srcBuf.get();
					float out;
					if (gamma == 1.0) {
						out = in;
					} else {
						out = (float)Math.pow(in, gamma);
					}
					dstBuf.put(out);
				}
			}
			srcLine += srcStep;
			dstLine += dstStep;
		}
	}

	public static void flipCopyWithGamma( DoubleBuffer srcBuf, int srcStep,
										  DoubleBuffer dstBuf, int dstStep, double gamma, boolean flip, int channels ) {
		assert srcBuf != dstBuf;
		int w = Math.min(srcStep, dstStep);
		int srcLine = srcBuf.position(), dstLine = dstBuf.position();
		double[] buffer = new double[channels];
		while (srcLine < srcBuf.capacity() && dstLine < dstBuf.capacity()) {
			if (flip) {
				srcBuf.position(srcBuf.capacity() - srcLine - srcStep);
			} else {
				srcBuf.position(srcLine);
			}
			dstBuf.position(dstLine);
			w = Math.min(Math.min(w, srcBuf.remaining()), dstBuf.remaining());
			if (channels > 1) {
				for (int x = 0; x < w; x += channels) {
					for (int z = 0; z < channels; z++) {
						double in = srcBuf.get();
						double out;
						if (gamma == 1.0) {
							out = in;
						} else {
							out = Math.pow(in, gamma);
						}
						buffer[z] = out;
					}
					for (int z = channels - 1; z >= 0; z--) {
						dstBuf.put(buffer[z]);
					}
				}
			} else {
				for (int x = 0; x < w; x++) {
					double in = srcBuf.get();
					double out;
					if (gamma == 1.0) {
						out = in;
					} else {
						out = Math.pow(in, gamma);
					}
					dstBuf.put(out);
				}
			}
			srcLine += srcStep;
			dstLine += dstStep;
		}
	}

	public static void applyGamma( Frame frame, double gamma ) {
		applyGamma(frame.image[0].position(0), frame.imageDepth, frame.imageStride, gamma);
	}

	public static void applyGamma( Buffer buffer, int depth, int stride, double gamma ) {
		if (gamma == 1.0) {
			return;
		}
		switch (depth) {
			case Frame.DEPTH_UBYTE:
				flipCopyWithGamma(((ByteBuffer)buffer).asReadOnlyBuffer(), stride, (ByteBuffer)buffer, stride, false, gamma, false, 0);
				break;
			case Frame.DEPTH_BYTE:
				flipCopyWithGamma(((ByteBuffer)buffer).asReadOnlyBuffer(), stride, (ByteBuffer)buffer, stride, true, gamma, false, 0);
				break;
			case Frame.DEPTH_USHORT:
				flipCopyWithGamma(((ShortBuffer)buffer).asReadOnlyBuffer(), stride, (ShortBuffer)buffer, stride, false, gamma, false, 0);
				break;
			case Frame.DEPTH_SHORT:
				flipCopyWithGamma(((ShortBuffer)buffer).asReadOnlyBuffer(), stride, (ShortBuffer)buffer, stride, true, gamma, false, 0);
				break;
			case Frame.DEPTH_INT:
				flipCopyWithGamma(((IntBuffer)buffer).asReadOnlyBuffer(), stride, (IntBuffer)buffer, stride, gamma, false, 0);
				break;
			case Frame.DEPTH_FLOAT:
				flipCopyWithGamma(((FloatBuffer)buffer).asReadOnlyBuffer(), stride, (FloatBuffer)buffer, stride, gamma, false, 0);
				break;
			case Frame.DEPTH_DOUBLE:
				flipCopyWithGamma(((DoubleBuffer)buffer).asReadOnlyBuffer(), stride, (DoubleBuffer)buffer, stride, gamma, false, 0);
				break;
			default:
				assert false;
		}
	}

	public static void copy( Frame frame, BufferedImage bufferedImage ) {
		copy(frame, bufferedImage, 1.0);
	}

	public static void copy( Frame frame, BufferedImage bufferedImage, double gamma ) {
		copy(frame, bufferedImage, gamma, false, null);
	}

	public static void copy( Frame frame, BufferedImage bufferedImage, double gamma, boolean flipChannels, Rectangle roi ) {
		Buffer in = frame.image[0].position(roi == null ? 0 : roi.y*frame.imageStride + roi.x*frame.imageChannels);
		SampleModel sm = bufferedImage.getSampleModel();
		Raster r = bufferedImage.getRaster();
		DataBuffer out = r.getDataBuffer();
		int x = -r.getSampleModelTranslateX();
		int y = -r.getSampleModelTranslateY();
		int step = sm.getWidth()*sm.getNumBands();
		int channels = sm.getNumBands();
		if (sm instanceof ComponentSampleModel) {
			step = ((ComponentSampleModel)sm).getScanlineStride();
			channels = ((ComponentSampleModel)sm).getPixelStride();
		} else if (sm instanceof SinglePixelPackedSampleModel) {
			step = ((SinglePixelPackedSampleModel)sm).getScanlineStride();
			channels = 1;
		} else if (sm instanceof MultiPixelPackedSampleModel) {
			step = ((MultiPixelPackedSampleModel)sm).getScanlineStride();
			channels = ((MultiPixelPackedSampleModel)sm).getPixelBitStride()/8; // ??
		}
		int start = y*step + x*channels;

		if (out instanceof DataBufferByte) {
			byte[] a = ((DataBufferByte)out).getData();
			flipCopyWithGamma((ByteBuffer)in, frame.imageStride, ByteBuffer.wrap(a, start, a.length - start), step, false, gamma, false, flipChannels ? channels : 0);
		} else if (out instanceof DataBufferDouble) {
			double[] a = ((DataBufferDouble)out).getData();
			flipCopyWithGamma((DoubleBuffer)in, frame.imageStride, DoubleBuffer.wrap(a, start, a.length - start), step, gamma, false, flipChannels ? channels : 0);
		} else if (out instanceof DataBufferFloat) {
			float[] a = ((DataBufferFloat)out).getData();
			flipCopyWithGamma((FloatBuffer)in, frame.imageStride, FloatBuffer.wrap(a, start, a.length - start), step, gamma, false, flipChannels ? channels : 0);
		} else if (out instanceof DataBufferInt) {
			int[] a = ((DataBufferInt)out).getData();
			int stride = frame.imageStride;
			if (in instanceof ByteBuffer) {
				in = ((ByteBuffer)in).order(flipChannels ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).asIntBuffer();
				stride /= 4;
			}
			flipCopyWithGamma((IntBuffer)in, stride, IntBuffer.wrap(a, start, a.length - start), step, gamma, false, flipChannels ? channels : 0);
		} else if (out instanceof DataBufferShort) {
			short[] a = ((DataBufferShort)out).getData();
			flipCopyWithGamma((ShortBuffer)in, frame.imageStride, ShortBuffer.wrap(a, start, a.length - start), step, true, gamma, false, flipChannels ? channels : 0);
		} else if (out instanceof DataBufferUShort) {
			short[] a = ((DataBufferUShort)out).getData();
			flipCopyWithGamma((ShortBuffer)in, frame.imageStride, ShortBuffer.wrap(a, start, a.length - start), step, false, gamma, false, flipChannels ? channels : 0);
		} else {
			assert false;
		}
	}

	public static void copy( BufferedImage image, Frame frame ) {
		copy(image, frame, 1.0);
	}

	public static void copy( BufferedImage image, Frame frame, double gamma ) {
		copy(image, frame, gamma, false, null);
	}

	public static void copy( BufferedImage image, Frame frame, double gamma, boolean flipChannels, Rectangle roi ) {
		Buffer out = frame.image[0].position(roi == null ? 0 : roi.y*frame.imageStride + roi.x*frame.imageChannels);
		SampleModel sm = image.getSampleModel();
		Raster r = image.getRaster();
		DataBuffer in = r.getDataBuffer();
		int x = -r.getSampleModelTranslateX();
		int y = -r.getSampleModelTranslateY();
		int step = sm.getWidth()*sm.getNumBands();
		int channels = sm.getNumBands();
		if (sm instanceof ComponentSampleModel) {
			step = ((ComponentSampleModel)sm).getScanlineStride();
			channels = ((ComponentSampleModel)sm).getPixelStride();
		} else if (sm instanceof SinglePixelPackedSampleModel) {
			step = ((SinglePixelPackedSampleModel)sm).getScanlineStride();
			channels = 1;
		} else if (sm instanceof MultiPixelPackedSampleModel) {
			step = ((MultiPixelPackedSampleModel)sm).getScanlineStride();
			channels = ((MultiPixelPackedSampleModel)sm).getPixelBitStride()/8; // ??
		}
		int start = y*step + x*channels;

		if (in instanceof DataBufferByte) {
			byte[] a = ((DataBufferByte)in).getData();
			flipCopyWithGamma(ByteBuffer.wrap(a, start, a.length - start), step, (ByteBuffer)out, frame.imageStride, false, gamma, false, flipChannels ? channels : 0);
		} else if (in instanceof DataBufferDouble) {
			double[] a = ((DataBufferDouble)in).getData();
			flipCopyWithGamma(DoubleBuffer.wrap(a, start, a.length - start), step, (DoubleBuffer)out, frame.imageStride, gamma, false, flipChannels ? channels : 0);
		} else if (in instanceof DataBufferFloat) {
			float[] a = ((DataBufferFloat)in).getData();
			flipCopyWithGamma(FloatBuffer.wrap(a, start, a.length - start), step, (FloatBuffer)out, frame.imageStride, gamma, false, flipChannels ? channels : 0);
		} else if (in instanceof DataBufferInt) {
			int[] a = ((DataBufferInt)in).getData();
			int stride = frame.imageStride;
			if (out instanceof ByteBuffer) {
				out = ((ByteBuffer)out).order(flipChannels ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN).asIntBuffer();
				stride /= 4;
			}
			flipCopyWithGamma(IntBuffer.wrap(a, start, a.length - start), step, (IntBuffer)out, stride, gamma, false, flipChannels ? channels : 0);
		} else if (in instanceof DataBufferShort) {
			short[] a = ((DataBufferShort)in).getData();
			flipCopyWithGamma(ShortBuffer.wrap(a, start, a.length - start), step, (ShortBuffer)out, frame.imageStride, true, gamma, false, flipChannels ? channels : 0);
		} else if (in instanceof DataBufferUShort) {
			short[] a = ((DataBufferUShort)in).getData();
			flipCopyWithGamma(ShortBuffer.wrap(a, start, a.length - start), step, (ShortBuffer)out, frame.imageStride, false, gamma, false, flipChannels ? channels : 0);
		} else {
			assert false;
		}
	}

	protected BufferedImage bufferedImage = null;

	public static int getBufferedImageType( Frame frame ) {
		// precanned BufferedImage types are confusing... in practice though,
		// they all use the sRGB color model when blitting:
		//     http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5051418
		// and we should use them because they are *A LOT* faster with Java 2D.
		// workaround: do gamma correction ourselves ("gamma" parameter)
		//             since we'll never use getRGB() and setRGB(), right?
		int type = BufferedImage.TYPE_CUSTOM;
		if (frame.imageChannels == 1) {
			if (frame.imageDepth == Frame.DEPTH_UBYTE || frame.imageDepth == Frame.DEPTH_BYTE) {
				type = BufferedImage.TYPE_BYTE_GRAY;
			} else if (frame.imageDepth == Frame.DEPTH_USHORT) {
				type = BufferedImage.TYPE_USHORT_GRAY;
			}
		} else if (frame.imageChannels == 3) {
			if (frame.imageDepth == Frame.DEPTH_UBYTE || frame.imageDepth == Frame.DEPTH_BYTE) {
				type = BufferedImage.TYPE_3BYTE_BGR;
			}
		} else if (frame.imageChannels == 4) {
			// The channels end up reversed of what we need for OpenCL.
			// We work around this in copyTo() and copyFrom() by
			// inversing the channels to let us use RGBA in our IplImage.
			if (frame.imageDepth == Frame.DEPTH_UBYTE || frame.imageDepth == Frame.DEPTH_BYTE) {
				type = BufferedImage.TYPE_4BYTE_ABGR;
			}
		}
		return type;
	}

	public BufferedImage getBufferedImage( Frame frame ) {
		return getBufferedImage(frame, 1.0);
	}

	public BufferedImage getBufferedImage( Frame frame, double gamma ) {
		return getBufferedImage(frame, gamma, false, null);
	}

	public BufferedImage getBufferedImage( Frame frame, double gamma, boolean flipChannels, ColorSpace cs ) {
		if (frame == null || frame.image == null) {
			return null;
		}
		int type = getBufferedImageType(frame);

		if (bufferedImage == null || bufferedImage.getWidth() != frame.imageWidth
				|| bufferedImage.getHeight() != frame.imageHeight || bufferedImage.getType() != type) {
			bufferedImage = type == BufferedImage.TYPE_CUSTOM || cs != null ? null
					: new BufferedImage(frame.imageWidth, frame.imageHeight, type);
		}

		if (bufferedImage == null) {
			boolean alpha = false;
			int[] offsets = null;
			if (frame.imageChannels == 1) {
				alpha = false;
				if (cs == null) {
					cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
				}
				offsets = new int[]{0};
			} else if (frame.imageChannels == 3) {
				alpha = false;
				if (cs == null) {
					cs = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
				}
				// raster in "BGR" order like OpenCV..
				offsets = new int[]{2, 1, 0};
			} else if (frame.imageChannels == 4) {
				alpha = true;
				if (cs == null) {
					cs = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
				}
				// raster in "RGBA" order for OpenCL.. alpha needs to be last
				offsets = new int[]{0, 1, 2, 3};
			} else {
				assert false;
			}

			ColorModel cm = null;
			WritableRaster wr = null;
			if (frame.imageDepth == Frame.DEPTH_UBYTE || frame.imageDepth == Frame.DEPTH_BYTE) {
				cm = new ComponentColorModel(cs, alpha,
						false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
				wr = Raster.createWritableRaster(new ComponentSampleModel(
						DataBuffer.TYPE_BYTE, frame.imageWidth, frame.imageHeight, frame.imageChannels, frame.imageStride,
						offsets), null);
			} else if (frame.imageDepth == Frame.DEPTH_USHORT) {
				cm = new ComponentColorModel(cs, alpha,
						false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
				wr = Raster.createWritableRaster(new ComponentSampleModel(
						DataBuffer.TYPE_USHORT, frame.imageWidth, frame.imageHeight, frame.imageChannels, frame.imageStride,
						offsets), null);
			} else if (frame.imageDepth == Frame.DEPTH_SHORT) {
				cm = new ComponentColorModel(cs, alpha,
						false, Transparency.OPAQUE, DataBuffer.TYPE_SHORT);
				wr = Raster.createWritableRaster(new ComponentSampleModel(
						DataBuffer.TYPE_SHORT, frame.imageWidth, frame.imageHeight, frame.imageChannels, frame.imageStride,
						offsets), null);
			} else if (frame.imageDepth == Frame.DEPTH_INT) {
				cm = new ComponentColorModel(cs, alpha,
						false, Transparency.OPAQUE, DataBuffer.TYPE_INT);
				wr = Raster.createWritableRaster(new ComponentSampleModel(
						DataBuffer.TYPE_INT, frame.imageWidth, frame.imageHeight, frame.imageChannels, frame.imageStride,
						offsets), null);
			} else if (frame.imageDepth == Frame.DEPTH_FLOAT) {
				cm = new ComponentColorModel(cs, alpha,
						false, Transparency.OPAQUE, DataBuffer.TYPE_FLOAT);
				wr = Raster.createWritableRaster(new ComponentSampleModel(
						DataBuffer.TYPE_FLOAT, frame.imageWidth, frame.imageHeight, frame.imageChannels, frame.imageStride,
						offsets), null);
			} else if (frame.imageDepth == Frame.DEPTH_DOUBLE) {
				cm = new ComponentColorModel(cs, alpha,
						false, Transparency.OPAQUE, DataBuffer.TYPE_DOUBLE);
				wr = Raster.createWritableRaster(new ComponentSampleModel(
						DataBuffer.TYPE_DOUBLE, frame.imageWidth, frame.imageHeight, frame.imageChannels, frame.imageStride,
						offsets), null);
			} else {
				assert false;
			}

			bufferedImage = new BufferedImage(cm, wr, false, null);
		}

		if (bufferedImage != null) {
			copy(frame, bufferedImage, gamma, flipChannels, null);
		}

		return bufferedImage;
	}

	/**
	 * Returns a Frame based on a BufferedImage.
	 */
	public Frame getFrame( BufferedImage image ) {
		return getFrame(image, 1.0);
	}

	/**
	 * Returns a Frame based on a BufferedImage, and given gamma.
	 */
	public Frame getFrame( BufferedImage image, double gamma ) {
		return getFrame(image, gamma, false);
	}

	/**
	 * Returns a Frame based on a BufferedImage, given gamma, and inverted channels flag.
	 */
	public Frame getFrame( BufferedImage image, double gamma, boolean flipChannels ) {
		if (image == null) {
			return null;
		}
		SampleModel sm = image.getSampleModel();
		int depth = 0, numChannels = sm.getNumBands();
		switch (image.getType()) {
			case BufferedImage.TYPE_INT_RGB:
			case BufferedImage.TYPE_INT_ARGB:
			case BufferedImage.TYPE_INT_ARGB_PRE:
			case BufferedImage.TYPE_INT_BGR:
				depth = Frame.DEPTH_UBYTE;
				numChannels = 4;
				break;
		}
		if (depth == 0 || numChannels == 0) {
			switch (sm.getDataType()) {
				case DataBuffer.TYPE_BYTE:   depth = Frame.DEPTH_UBYTE;  break;
				case DataBuffer.TYPE_USHORT: depth = Frame.DEPTH_USHORT; break;
				case DataBuffer.TYPE_SHORT:  depth = Frame.DEPTH_SHORT;  break;
				case DataBuffer.TYPE_INT:    depth = Frame.DEPTH_INT;    break;
				case DataBuffer.TYPE_FLOAT:  depth = Frame.DEPTH_FLOAT;  break;
				case DataBuffer.TYPE_DOUBLE: depth = Frame.DEPTH_DOUBLE; break;
				default: assert false;
			}
		}
		if (frame == null || frame.imageWidth != image.getWidth() || frame.imageHeight != image.getHeight()
				|| frame.imageDepth != depth || frame.imageChannels != numChannels) {
			frame = new Frame(image.getWidth(), image.getHeight(), depth, numChannels);
		}
		copy(image, frame, gamma, flipChannels, null);
		return frame;
	}
}